// here, the extra check for window['Ext'] is needed for use with cmd-test
// code injection.  we need to make that this file will sync up with page global
// scope to avoid duplicate Ext.Boot state.  That check is after the initial Ext check
// to allow the sandboxing template to inject an appropriate Ext var and prevent the
// global detection.
var Ext = Ext || window['Ext'] || {};
//<editor-fold desc="Microloader">
/**
 * @class Ext.Microloader
 * @private
 * @singleton
 */
Ext.Microloader = Ext.Microloader || (function() {
  var Boot = Ext.Boot,
    //<debug>
    _debug = function(message) {
      //console.log(message);
    },
    //</debug>
    _warn = function(message) {
      console.log("[WARN] " + message);
    },
    _privatePrefix = '_ext:' + location.pathname,
    /**
     * @method getStorageKey
     * The Following combination is used to create isolated local storage keys
     * '_ext' is used to scope all the local storage keys that we internally by Ext
     * 'location.pathname' is used to force each assets to cache by an absolute URL (/build/MyApp) (dev vs prod)
     * 'url' is used to force each asset to cache relative to the page (app.json vs resources/app.css)
     * 'profileId' is used to differentiate the builds of an application (neptune vs crisp)
     * 'Microloader.appId' is unique to the application and will differentiate apps on the same host (dev mode running app watch against multiple apps)
     */
    getStorageKey = function(url, profileId) {
      return _privatePrefix + url + '-' + (profileId ? profileId + '-' : '') + Microloader.appId;
    }, postProcessor, _storage;
  try {
    _storage = window['localStorage'];
  } catch (ex) {
    // ignore
  }
  var _cache = window['applicationCache'],
    // Local Storage Controller
    LocalStorage = {
      clearAllPrivate : function(manifest) {
        if (_storage) {
          //Remove the entry for the manifest first
          _storage.removeItem(manifest.key);
          var i, key,
            removeKeys = [],
            suffix = manifest.profile + '-' + Microloader.appId,
            ln = _storage.length;
          for (i = 0; i < ln; i++) {
            key = _storage.key(i);
            // If key starts with the private key and the suffix is present we can clear this entry
            if (key.indexOf(_privatePrefix) === 0 && key.indexOf(suffix) !== -1) {
              removeKeys.push(key);
            }
          }
          for (i in removeKeys) {
            //<debug>
            _debug("Removing " + removeKeys[i] + " from Local Storage");
            //</debug>
            _storage.removeItem(removeKeys[i]);
          }
        }
      },
      /**
       * @private
       */
      retrieveAsset : function(key) {
        try {
          return _storage.getItem(key);
        } catch (e) {
          // Private browsing mode
          return null;
        }
      },
      setAsset : function(key, content) {
        try {
          if (content === null || content == '') {
            _storage.removeItem(key);
          } else {
            _storage.setItem(key, content);
          }
        } catch (e) {
          if (_storage && e.code == e.QUOTA_EXCEEDED_ERR) {
            //<debug>
            _warn("LocalStorage Quota exceeded, cannot store " + key + " locally");
            //</debug>
          }
        }
      }
    };
  var Asset = function(cfg) {
    if (typeof cfg.assetConfig === 'string') {
      this.assetConfig = {
        path : cfg.assetConfig
      };
    } else {
      this.assetConfig = cfg.assetConfig;
    }
    this.type = cfg.type;
    this.key = getStorageKey(this.assetConfig.path, cfg.manifest.profile);
    if (cfg.loadFromCache) {
      this.loadFromCache();
    }
  };
  Asset.prototype = {
    shouldCache : function() {
      return _storage && this.assetConfig.update && this.assetConfig.hash && !this.assetConfig.remote;
    },
    is : function(asset) {
      return (!!asset && this.assetConfig && asset.assetConfig && (this.assetConfig.hash === asset.assetConfig.hash))
    },
    cache : function(content) {
      if (this.shouldCache()) {
        LocalStorage.setAsset(this.key, content || this.content);
      }
    },
    uncache : function() {
      LocalStorage.setAsset(this.key, null);
    },
    updateContent : function(content) {
      this.content = content;
    },
    getSize : function() {
      return this.content ? this.content.length : 0;
    },
    loadFromCache : function() {
      if (this.shouldCache()) {
        this.content = LocalStorage.retrieveAsset(this.key);
      }
    }
  };
  var Manifest = function(cfg) {
    if (typeof cfg.content === "string") {
      this.content = JSON.parse(cfg.content);
    } else {
      this.content = cfg.content;
    }
    this.assetMap = {};
    this.url = cfg.url;
    this.fromCache = !!cfg.cached;
    this.assetCache = !(cfg.assetCache === false);
    this.key = getStorageKey(this.url);
    // Pull out select properties for repetitive use
    this.profile = this.content.profile;
    this.hash = this.content.hash;
    this.loadOrder = this.content.loadOrder;
    this.deltas = this.content.cache ? this.content.cache.deltas : null;
    this.cacheEnabled = this.content.cache ? this.content.cache.enable : false;
    this.loadOrderMap = (this.loadOrder) ? Boot.createLoadOrderMap(this.loadOrder) : null;
    var tags = this.content.tags,
      platformTags = Ext.platformTags;
    if (tags) {
      if (tags instanceof Array) {
        for (var i = 0; i < tags.length; i++) {
          platformTags[tags[i]] = true;
        }
      } else {
        Boot.apply(platformTags, tags);
      }
      // re-apply the query parameters, so that the params as specified
      // in the url always has highest priority
      Boot.apply(platformTags, Boot.loadPlatformsParam());
    }
    // Convert all assets into Assets
    this.js = this.processAssets(this.content.js, 'js');
    this.css = this.processAssets(this.content.css, 'css');
  };
  Manifest.prototype = {
    processAsset : function(assetConfig, type) {
      var processedAsset = new Asset({
        manifest : this,
        assetConfig : assetConfig,
        type : type,
        loadFromCache : this.assetCache
      });
      this.assetMap[assetConfig.path] = processedAsset;
      return processedAsset;
    },
    processAssets : function(assets, type) {
      var results = [],
        ln = assets.length, i, assetConfig;
      for (i = 0; i < ln; i++) {
        assetConfig = assets[i];
        results.push(this.processAsset(assetConfig, type));
      }
      return results;
    },
    useAppCache : function() {
      return true;
    },
    // Concatenate all assets for easy access
    getAssets : function() {
      return this.css.concat(this.js);
    },
    getAsset : function(path) {
      return this.assetMap[path];
    },
    shouldCache : function() {
      return this.hash && this.cacheEnabled;
    },
    cache : function(content) {
      if (this.shouldCache()) {
        LocalStorage.setAsset(this.key, JSON.stringify(content || this.content));
      }
      //<debug>
      else {
        _debug("Manifest caching is disabled.");
      }
      //</debug>
    },
    is : function(manifest) {
      //<debug>
      _debug("Testing Manifest: " + this.hash + " VS " + manifest.hash);
      //</debug>
      return this.hash === manifest.hash;
    },
    // Clear the manifest from local storage
    uncache : function() {
      LocalStorage.setAsset(this.key, null);
    },
    exportContent : function() {
      return Boot.apply({
        loadOrderMap : this.loadOrderMap
      }, this.content);
    }
  };
  /**
   * Microloader
   * @type {Array}
   * @private
   */
  var _listeners = [],
    _loaded = false,
    Microloader = {
      init : function() {
        Ext.microloaded = true;
        // data-app is in the dev template for an application and is also
        // injected into the app my CMD for production
        // We use this to prefix localStorage cache to prevent collisions
        var microloaderElement = document.getElementById('microloader');
        Microloader.appId = microloaderElement ? microloaderElement.getAttribute('data-app') : '';
        if (Ext.beforeLoad) {
          postProcessor = Ext.beforeLoad(Ext.platformTags);
        }
        var readyHandler = Ext._beforereadyhandler;
        Ext._beforereadyhandler = function() {
          if (Ext.Boot !== Boot) {
            Ext.apply(Ext.Boot, Boot);
            Ext.Boot = Boot;
          }
          if (readyHandler) {
            readyHandler();
          }
        };
      },
      applyCacheBuster : function(url) {
        var tstamp = new Date().getTime(),
          sep = url.indexOf('?') === -1 ? '?' : '&';
        url = url + sep + "_dc=" + tstamp;
        return url;
      },
      run : function() {
        Microloader.init();
        var manifest = Ext.manifest;
        if (typeof manifest === "string") {
          var extension = ".json",
            url = manifest.indexOf(extension) === manifest.length - extension.length ? manifest : manifest + ".json",
            key = getStorageKey(url),
            content = LocalStorage.retrieveAsset(key);
          // Manifest found in local storage, use this for immediate boot except in PhantomJS environments for building.
          if (content) {
            //<debug>
            _debug("Manifest file, '" + url + "', was found in Local Storage");
            //</debug>
            manifest = new Manifest({
              url : url,
              content : content,
              cached : true
            });
            if (postProcessor) {
              postProcessor(manifest);
            }
            Microloader.load(manifest);
            // Manifest is not in local storage. Fetch it from the server
          } else {
            //<debug>
            _debug("Manifest file was not found in Local Storage, loading: " + url);
            //</debug>
            if (location.href.indexOf('file:/') === 0) {
              Manifest.url = Microloader.applyCacheBuster(url + 'p');
              Boot.load(Manifest.url);
            } else {
              Manifest.url = url;
              Boot.fetch(Microloader.applyCacheBuster(url), function(result) {
                Microloader.setManifest(result.content);
              });
            }
          }
          // Embedded Manifest into JS file
        } else {
          //<debug>
          _debug("Manifest was embedded into application javascript file");
          //</debug>
          manifest = new Manifest({
            content : manifest
          });
          Microloader.load(manifest);
        }
      },
      /**
       *
       * @param cfg
       */
      setManifest : function(cfg) {
        var manifest = new Manifest({
          url : Manifest.url,
          content : cfg
        });
        manifest.cache();
        if (postProcessor) {
          postProcessor(manifest);
        }
        Microloader.load(manifest);
      },
      /**
       * @param {Manifest} manifest
       */
      load : function(manifest) {
        Microloader.urls = [];
        Microloader.manifest = manifest;
        Ext.manifest = Microloader.manifest.exportContent();
        var assets = manifest.getAssets(),
          cachedAssets = [], asset, i, len, include, entry;
        for (len = assets.length, i = 0; i < len; i++) {
          asset = assets[i];
          include = Microloader.filterAsset(asset);
          if (include) {
            // Asset is using the localStorage caching system
            if (manifest.shouldCache() && asset.shouldCache()) {
              // Asset already has content from localStorage, instantly seed that into boot
              if (asset.content) {
                //<debug>
                _debug("Asset: " + asset.assetConfig.path + " was found in local storage. No remote load for this file");
                //</debug>
                entry = Boot.registerContent(asset.assetConfig.path, asset.type, asset.content);
                if (entry.evaluated) {
                  _warn("Asset: " + asset.assetConfig.path + " was evaluated prior to local storage being consulted.");
                }
                //load via AJAX and seed content into Boot
              } else {
                //<debug>
                _debug("Asset: " + asset.assetConfig.path + " was NOT found in local storage. Adding to load queue");
                //</debug>
                cachedAssets.push(asset);
              }
            }
            Microloader.urls.push(asset.assetConfig.path);
            Boot.assetConfig[asset.assetConfig.path] = Boot.apply({
              type : asset.type
            }, asset.assetConfig);
          }
        }
        // If any assets are using the caching system and do not have local versions load them first via AJAX
        if (cachedAssets.length > 0) {
          Microloader.remainingCachedAssets = cachedAssets.length;
          while (cachedAssets.length > 0) {
            asset = cachedAssets.pop();
            //<debug>
            _debug("Preloading/Fetching Cached Assets from: " + asset.assetConfig.path);
            //</debug>
            Boot.fetch(asset.assetConfig.path, (function(asset) {
              return function(result) {
                Microloader.onCachedAssetLoaded(asset, result);
              }
            })(asset));
          }
        } else {
          Microloader.onCachedAssetsReady();
        }
      },
      // Load the asset and seed its content into Boot to be evaluated in sequence
      onCachedAssetLoaded : function(asset, result) {
        var checksum;
        result = Microloader.parseResult(result);
        Microloader.remainingCachedAssets--;
        if (!result.error) {
          checksum = Microloader.checksum(result.content, asset.assetConfig.hash);
          if (!checksum) {
            _warn("Cached Asset '" + asset.assetConfig.path
                + "' has failed checksum. This asset will be uncached for future loading");
            // Un cache this asset so it is loaded next time
            asset.uncache();
          }
          //<debug>
          _debug("Checksum for Cached Asset: " + asset.assetConfig.path + " is " + checksum);
          //</debug>
          Boot.registerContent(asset.assetConfig.path, asset.type, result.content);
          asset.updateContent(result.content);
          asset.cache();
        } else {
          _warn("There was an error pre-loading the asset '" + asset.assetConfig.path
              + "'. This asset will be uncached for future loading");
          // Un cache this asset so it is loaded next time
          asset.uncache();
        }
        if (Microloader.remainingCachedAssets === 0) {
          Microloader.onCachedAssetsReady();
        }
      },
      onCachedAssetsReady : function() {
        Boot.load({
          url : Microloader.urls,
          loadOrder : Microloader.manifest.loadOrder,
          loadOrderMap : Microloader.manifest.loadOrderMap,
          sequential : true,
          success : Microloader.onAllAssetsReady,
          failure : Microloader.onAllAssetsReady
        });
      },
      onAllAssetsReady : function() {
        _loaded = true;
        Microloader.notify();
        if (navigator.onLine !== false) {
          //<debug>
          _debug("Application is online, checking for updates");
          //</debug>
          Microloader.checkAllUpdates();
        } else {
          //<debug>
          _debug("Application is offline, adding online listener to check for updates");
          //</debug>
          if (window['addEventListener']) {
            window.addEventListener('online', Microloader.checkAllUpdates, false);
          }
        }
      },
      onMicroloaderReady : function(listener) {
        if (_loaded) {
          listener();
        } else {
          _listeners.push(listener);
        }
      },
      /**
       * @private
       */
      notify : function() {
        //<debug>
        _debug("notifying microloader ready listeners.");
        //</debug>
        var listener;
        while ((listener = _listeners.shift())) {
          listener();
        }
      },
      // Delta patches content
      patch : function(content, delta) {
        var output = [], chunk, i, ln;
        if (delta.length === 0) { return content; }
        for (i = 0, ln = delta.length; i < ln; i++) {
          chunk = delta[i];
          if (typeof chunk === 'number') {
            output.push(content.substring(chunk, chunk + delta[++i]));
          } else {
            output.push(chunk);
          }
        }
        return output.join('');
      },
      checkAllUpdates : function() {
        //<debug>
        _debug("Checking for All Updates");
        //</debug>
        if (window['removeEventListener']) {
          window.removeEventListener('online', Microloader.checkAllUpdates, false);
        }
        if (_cache) {
          Microloader.checkForAppCacheUpdate();
        }
        // Manifest came from a cached instance, check for updates
        if (Microloader.manifest.fromCache) {
          Microloader.checkForUpdates();
        }
      },
      checkForAppCacheUpdate : function() {
        //<debug>
        _debug("Checking App Cache status");
        //</debug>
        if (_cache.status === _cache.UPDATEREADY || _cache.status === _cache.OBSOLETE) {
          //<debug>
          _debug("App Cache is already in an updated");
          //</debug>
          Microloader.appCacheState = 'updated';
        } else if (_cache.status !== _cache.IDLE && _cache.status !== _cache.UNCACHED) {
          //<debug>
          _debug("App Cache is checking or downloading updates, adding listeners");
          //</debug>
          Microloader.appCacheState = 'checking';
          _cache.addEventListener('error', Microloader.onAppCacheError);
          _cache.addEventListener('noupdate', Microloader.onAppCacheNotUpdated);
          _cache.addEventListener('cached', Microloader.onAppCacheNotUpdated);
          _cache.addEventListener('updateready', Microloader.onAppCacheReady);
          _cache.addEventListener('obsolete', Microloader.onAppCacheObsolete);
        } else {
          //<debug>
          _debug("App Cache is current or uncached");
          //</debug>
          Microloader.appCacheState = 'current';
        }
      },
      checkForUpdates : function() {
        // Fetch the Latest Manifest from the server
        //<debug>
        _debug("Checking for updates at: " + Microloader.manifest.url);
        //</debug>
        Boot.fetch(Microloader.applyCacheBuster(Microloader.manifest.url), Microloader.onUpdatedManifestLoaded);
      },
      onAppCacheError : function(e) {
        _warn(e.message);
        Microloader.appCacheState = 'error';
        Microloader.notifyUpdateReady();
      },
      onAppCacheReady : function() {
        _cache.swapCache();
        Microloader.appCacheUpdated();
      },
      onAppCacheObsolete : function() {
        Microloader.appCacheUpdated();
      },
      appCacheUpdated : function() {
        //<debug>
        _debug("App Cache Updated");
        //</debug>
        Microloader.appCacheState = 'updated';
        Microloader.notifyUpdateReady();
      },
      onAppCacheNotUpdated : function() {
        //<debug>
        _debug("App Cache Not Updated Callback");
        //</debug>
        Microloader.appCacheState = 'current';
        Microloader.notifyUpdateReady();
      },
      filterAsset : function(asset) {
        var cfg = (asset && asset.assetConfig) || {};
        if (cfg.platform || cfg.exclude) { return Boot.filterPlatform(cfg.platform, cfg.exclude); }
        return true;
      },
      onUpdatedManifestLoaded : function(result) {
        result = Microloader.parseResult(result);
        if (!result.error) {
          var currentAssets, newAssets, currentAsset, newAsset, prop, assets, deltas, deltaPath, include,
            updatingAssets = [],
            manifest = new Manifest({
              url : Microloader.manifest.url,
              content : result.content,
              assetCache : false
            });
          Microloader.remainingUpdatingAssets = 0;
          Microloader.updatedAssets = [];
          Microloader.removedAssets = [];
          Microloader.updatedManifest = null;
          Microloader.updatedAssetsReady = false;
          // If the updated manifest has turned off caching we need to clear out all local storage
          // and trigger a appupdate as all content is now uncached
          if (!manifest.shouldCache()) {
            //<debug>
            _debug("New Manifest has caching disabled, clearing out any private storage");
            //</debug>
            Microloader.updatedManifest = manifest;
            LocalStorage.clearAllPrivate(manifest);
            Microloader.onAllUpdatedAssetsReady();
            return;
          }
          // Manifest itself has changed
          if (!Microloader.manifest.is(manifest)) {
            Microloader.updatedManifest = manifest;
            currentAssets = Microloader.manifest.getAssets();
            newAssets = manifest.getAssets();
            // Look through new assets for assets that do not exist or assets that have different versions
            for (prop in newAssets) {
              newAsset = newAssets[prop];
              currentAsset = Microloader.manifest.getAsset(newAsset.assetConfig.path);
              include = Microloader.filterAsset(newAsset);
              if (include && (!currentAsset || (newAsset.shouldCache() && (!currentAsset.is(newAsset))))) {
                //<debug>
                _debug("New/Updated Version of Asset: " + newAsset.assetConfig.path + " was found in new manifest");
                //</debug>
                updatingAssets.push({
                  _new : newAsset,
                  _current : currentAsset
                });
              }
            }
            // Look through current assets for stale/old assets that have been removed
            for (prop in currentAssets) {
              currentAsset = currentAssets[prop];
              newAsset = manifest.getAsset(currentAsset.assetConfig.path);
              //New version of this asset has been filtered out
              include = !Microloader.filterAsset(newAsset);
              if (!include || !newAsset || (currentAsset.shouldCache() && !newAsset.shouldCache())) {
                //<debug>
                _debug("Asset: "
                    + currentAsset.assetConfig.path
                    + " was not found in new manifest, has been filtered out or has been switched to not cache. Marked for removal");
                //</debug>
                Microloader.removedAssets.push(currentAsset);
              }
            }
            // Loop through all assets that need updating
            if (updatingAssets.length > 0) {
              Microloader.remainingUpdatingAssets = updatingAssets.length;
              while (updatingAssets.length > 0) {
                assets = updatingAssets.pop();
                newAsset = assets._new;
                currentAsset = assets._current;
                // Full Updates will simply download the file and replace its current content
                if (newAsset.assetConfig.update === "full" || !currentAsset) {
                  //<debug>
                  if (newAsset.assetConfig.update === "delta") {
                    _debug("Delta updated asset found without current asset available: " + newAsset.assetConfig.path
                        + " fetching full file");
                  } else {
                    _debug("Full update found for: " + newAsset.assetConfig.path + " fetching");
                  }
                  //</debug>
                  // Load the asset and cache its  its content into Boot to be evaluated in sequence
                  Boot.fetch(newAsset.assetConfig.path, (function(asset) {
                    return function(result) {
                      Microloader.onFullAssetUpdateLoaded(asset, result)
                    };
                  }(newAsset)));
                  // Delta updates will be given a delta patch
                } else if (newAsset.assetConfig.update === "delta") {
                  deltas = manifest.deltas;
                  deltaPath = deltas + "/" + newAsset.assetConfig.path + "/" + currentAsset.assetConfig.hash + ".json";
                  // Fetch the Delta Patch and update the contents of the asset
                  //<debug>
                  _debug("Delta update found for: " + newAsset.assetConfig.path + " fetching");
                  //</debug>
                  Boot.fetch(deltaPath, (function(asset, oldAsset) {
                    return function(result) {
                      Microloader.onDeltaAssetUpdateLoaded(asset, oldAsset, result)
                    };
                  }(newAsset, currentAsset)));
                }
              }
            } else {
              //<debug>
              _debug("No Assets needed updating");
              //</debug>
              Microloader.onAllUpdatedAssetsReady();
            }
          } else {
            //<debug>
            _debug("Manifest files have matching hash's");
            //</debug>
            Microloader.onAllUpdatedAssetsReady();
          }
        } else {
          _warn("Error loading manifest file to check for updates");
          Microloader.onAllUpdatedAssetsReady();
        }
      },
      onFullAssetUpdateLoaded : function(asset, result) {
        var checksum;
        result = Microloader.parseResult(result);
        Microloader.remainingUpdatingAssets--;
        if (!result.error) {
          checksum = Microloader.checksum(result.content, asset.assetConfig.hash);
          //<debug>
          _debug("Checksum for Full asset: " + asset.assetConfig.path + " is " + checksum);
          //</debug>
          if (!checksum) {
            //<debug>
            _debug("Full Update Asset: " + asset.assetConfig.path
                + " has failed checksum. This asset will be uncached for future loading");
            //</debug>
            // uncache this asset as there is a new version somewhere that has not been loaded.
            asset.uncache();
          } else {
            asset.updateContent(result.content);
            Microloader.updatedAssets.push(asset);
          }
        } else {
          //<debug>
          _debug("Error loading file at" + asset.assetConfig.path + ". This asset will be uncached for future loading");
          //</debug>
          // uncache this asset as there is a new version somewhere that has not been loaded.
          asset.uncache();
        }
        if (Microloader.remainingUpdatingAssets === 0) {
          Microloader.onAllUpdatedAssetsReady();
        }
      },
      onDeltaAssetUpdateLoaded : function(asset, oldAsset, result) {
        var json, checksum, content;
        result = Microloader.parseResult(result);
        Microloader.remainingUpdatingAssets--;
        if (!result.error) {
          //<debug>
          _debug("Delta patch loaded successfully, patching content");
          //</debug>
          try {
            json = JSON.parse(result.content);
            content = Microloader.patch(oldAsset.content, json);
            checksum = Microloader.checksum(content, asset.assetConfig.hash);
            //<debug>
            _debug("Checksum for Delta Patched asset: " + asset.assetConfig.path + " is " + checksum);
            //</debug>
            if (!checksum) {
              //<debug>
              _debug("Delta Update Asset: " + asset.assetConfig.path
                  + " has failed checksum. This asset will be uncached for future loading");
              //</debug>
              // uncache this asset as there is a new version somewhere that has not been loaded.
              asset.uncache();
            } else {
              asset.updateContent(content);
              Microloader.updatedAssets.push(asset);
            }
          } catch (e) {
            _warn("Error parsing delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash
                + " . This asset will be uncached for future loading");
            // uncache this asset as there is a new version somewhere that has not been loaded.
            asset.uncache();
          }
        } else {
          _warn("Error loading delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash
              + " . This asset will be uncached for future loading");
          // uncache this asset as there is a new version somewhere that has not been loaded.
          asset.uncache();
        }
        if (Microloader.remainingUpdatingAssets === 0) {
          Microloader.onAllUpdatedAssetsReady();
        }
      },
      //TODO: Make this all transaction based to allow for reverting if quota is exceeded
      onAllUpdatedAssetsReady : function() {
        var asset;
        Microloader.updatedAssetsReady = true;
        if (Microloader.updatedManifest) {
          while (Microloader.removedAssets.length > 0) {
            asset = Microloader.removedAssets.pop();
            //<debug>
            _debug("Asset: " + asset.assetConfig.path + " was removed, un-caching");
            //</debug>
            asset.uncache();
          }
          if (Microloader.updatedManifest) {
            //<debug>
            _debug("Manifest was updated, re-caching");
            //</debug>
            Microloader.updatedManifest.cache();
          }
          while (Microloader.updatedAssets.length > 0) {
            asset = Microloader.updatedAssets.pop();
            //<debug>
            _debug("Asset: " + asset.assetConfig.path + " was updated, re-caching");
            //</debug>
            asset.cache();
          }
        }
        Microloader.notifyUpdateReady();
      },
      notifyUpdateReady : function() {
        if (Microloader.appCacheState !== 'checking' && Microloader.updatedAssetsReady) {
          if (Microloader.appCacheState === 'updated' || Microloader.updatedManifest) {
            //<debug>
            _debug("There was an update here you will want to reload the app, trigger an event");
            //</debug>
            Microloader.appUpdate = {
              updated : true,
              app : Microloader.appCacheState === 'updated',
              manifest : Microloader.updatedManifest && Microloader.updatedManifest.exportContent()
            };
            Microloader.fireAppUpdate();
          }
          //<debug>
          else {
            _debug("AppCache and LocalStorage Cache are current, no updating needed");
            Microloader.appUpdate = {};
          }
          //</debug>
        }
      },
      fireAppUpdate : function() {
        if (Ext.GlobalEvents) {
          // We defer dispatching this event slightly in order to let the application finish loading
          // as we are still very early in the lifecycle
          Ext.defer(function() {
            Ext.GlobalEvents.fireEvent('appupdate', Microloader.appUpdate);
          }, 1000);
        }
      },
      checksum : function(content, hash) {
        if (!content || !hash) { return false; }
        var passed = true,
          hashLn = hash.length,
          checksumType = content.substring(0, 1);
        if (checksumType == '/') {
          if (content.substring(2, hashLn + 2) !== hash) {
            passed = false;
          }
        } else if (checksumType == 'f') {
          if (content.substring(10, hashLn + 10) !== hash) {
            passed = false;
          }
        } else if (checksumType == '.') {
          if (content.substring(1, hashLn + 1) !== hash) {
            passed = false;
          }
        }
        return passed;
      },
      parseResult : function(result) {
        var rst = {};
        if ((result.exception || result.status === 0) && !Boot.env.phantom) {
          rst.error = true;
        } else if ((result.status >= 200 && result.status < 300) || result.status === 304 || Boot.env.phantom
            || (result.status === 0 && result.content.length > 0)) {
          rst.content = result.content;
        } else {
          rst.error = true;
        }
        return rst;
      }
    };
  return Microloader;
}());
/**
 * @type {String/Object}
 */
Ext.manifest = Ext.manifest || "bootstrap";
Ext.Microloader.run();